package net.neptech.service.impl;

import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import net.neptech.constants.message.MessageTopics;
import net.neptech.constants.order.OrderTopics;
import net.neptech.dto.order.DelayMessage;
import net.neptech.dto.order.OrderDTO;
import net.neptech.dto.sys.DeptStaffVo;
import net.neptech.feign.OrderFeignClient;
import net.neptech.feign.SystemFeignClient;
import net.neptech.model.appointment.Appointment;
import net.neptech.model.dto.AppointmentDTO;
import net.neptech.model.dto.PopQueueDTO;
import net.neptech.model.emum.AnnErrorEnum;
import net.neptech.model.message.Message;
import net.neptech.model.sys.User;
import net.neptech.model.vo.AppointmentVo;
import net.neptech.repository.AppointmentRepository;
import net.neptech.service.IAppointmentService;
import net.neptech.service.IClnicStaffService;
import net.neptech.topics.ClinicStaffTopics;
import net.neptech.utils.BeanCopyUtils;
import net.neptech.utils.DateUtils;
import net.neptech.vm.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
@Slf4j
public class AppointmentServiceImpl implements IAppointmentService {

    private  final Integer expire = 1; //订单超时时间

     @Autowired
     private RedisTemplate<Object,Object> redisTemplate;
     @Autowired
     private KafkaTemplate<String, Message> kafkaTemplateMsg;

     @Autowired
     private AppointmentRepository appointmentRepository;
     @Autowired
     private IClnicStaffService clnicStaffService;

     @Autowired
     private OrderFeignClient orderFeignClient;

     @Autowired
     private KafkaTemplate<String,Object> kafkaTemplate;
     private final  String appSuccessKey = "appointment:success:";
     @Autowired
     private SystemFeignClient systemFeignClient;
     @Autowired
     private DelayQueueServiceImpl delayQueueService;
     @Autowired
     private RedisTemplate<String, DelayMessage> delayMessageRedisTemplate;


    //抢号预约  各个科室都有高并发

    /**
     * 预约
     * @param appointmentDTO
     * @return
     */
     @Override
     @Transactional
     public ResponseResult<Appointment> appointment(AppointmentDTO appointmentDTO){
         Long userId = appointmentDTO.getUserId();
          //锁id防止超卖 存在返回true 设置30秒过期时间以防网络异常死锁
         Boolean b = redisTemplate.hasKey(appSuccessKey + userId);
         if (b){
             log.info("lock:{}", "请支付待支付订单");
             return ResponseResult.fail(AnnErrorEnum.ORDER_PAYMENT_PENDING.getCode(), "请支付待支付订单");
         }
          Boolean lock = redisTemplate.opsForValue().
                          setIfAbsent("appointment:lock" + userId, 1,5, TimeUnit.SECONDS);
         log.info("lock状态{}",lock );
         Appointment appointment = null;
         //lock 成功返回true
          if(Boolean.TRUE.equals(lock)){

               String key = "xxyy:doctor:"+appointmentDTO.getStaffId()+":clnicId:"+appointmentDTO.getClnicId();
               Object slot_no = redisTemplate.opsForList().leftPop(key);

               log.info("key:{}", key);
                if (slot_no == null) {
                    log.info("slot_no:{}", slot_no);
                    redisTemplate.delete("appointment:" + userId); // 约满了 删除锁
                    return ResponseResult.fail(AnnErrorEnum.SLOT_NOT_FOUND.getCode(), "预约失败，约满了 或没有该医生");
                }else {
                     //预约成功 更新预约单
                    //更新预约单
                    appointment = new Appointment();
                    appointment.setClnicId(appointmentDTO.getClnicId());
                    appointment.setStaffId(appointmentDTO.getStaffId());
                    appointment.setPatientId(appointmentDTO.getPatientId());
                    appointment.setSlotNo(Integer.parseInt(slot_no.toString()));
                    appointment.setPatientName(appointmentDTO.getPatientName());
                    //创建时间
                    //雪花id
                    Long orderId = IdUtil.getSnowflakeNextId();
                    Long appointmentId = IdUtil.getSnowflakeNextId();
                    appointment.setId(appointmentId);
                    appointment.setOrderId(orderId);
                    appointment.setBookedAt(appointmentDTO.getBookedAt());
                    //预约时间拆分 上午下午

                    if (appointmentDTO.getBookedAt().getHours() > 12)
                        appointment.setPeriod("下午");
                    else
                        appointment.setPeriod("上午");
                    //状态
                    appointment.setStatus(Byte.parseByte("0"));
                    //渠道 1线上 2.线下
                    appointment.setChannel(appointmentDTO.getTypeId().byteValue());
                    appointment.setUserId(userId);

                    //创建预约单
                    try {
                        Appointment save = appointmentRepository.save(appointment);
                        redisTemplate.opsForValue().set( appSuccessKey+userId,save,expire, TimeUnit.MINUTES);
                        log.info("预约成功，预约单id为：{}",save);
                        //创建订单  远程调用超时会有幂等性问题
                        OrderDTO orderDTO = new OrderDTO();
                        orderDTO.setTypeId(appointmentDTO.getTypeId());
                        orderDTO.setPatientId(appointmentDTO.getPatientId());
                        orderDTO.setStaffId(appointmentDTO.getStaffId());
                        orderDTO.setAmount(appointmentDTO.getAmount());
                        Date payDeadline = DateUtils.getCurrentTimePlusMinutes(expire);
                        orderDTO.setPayDeadline(payDeadline);
                        orderDTO.setUserId(userId);
                        orderDTO.setId(orderId);
                        orderFeignClient.createOrder(orderDTO);
                        //投放延迟队里 订单未支付 检查状态 未支付取消预约
                        delayQueueService.sendDelayMessage("ORDER_TIMEOUT", appointment, expire);

                        //扣减库存 投放队列中
                        kafkaTemplate.send(ClinicStaffTopics.SLOT_USE_UPDATE,"--slot",appointmentDTO);
                    } catch (Exception e) {
                        log.info("预约失败，预约号id为：{}",appointmentId);
                        redisTemplate.delete( appSuccessKey + userId);
                        redisTemplate.opsForList().leftPush(key,Integer.parseInt(slot_no.toString()));
                        //消息队列记录日志 人工处理 发短信通知都行
                        throw new RuntimeException(e);
                    }

                }
          }else {
               return ResponseResult.fail(AnnErrorEnum.LOCKED.getCode(),AnnErrorEnum.LOCKED.getMsg());
          }

          return ResponseResult.success(appointment);
     }

    /**
     * 取消预约
     * @param appointmentDTO
     * @return
     */
    @Override
    public ResponseResult<Boolean> concelAppointment(AppointmentDTO appointmentDTO) {

        Long userId = appointmentDTO.getUserId();
        Long clnicId = appointmentDTO.getClnicId();
        //取ttl 用于业务回滚
        Long expire = redisTemplate.getExpire( appSuccessKey + userId);
        Object appointment = redisTemplate.opsForValue().get( appSuccessKey + userId);
        log.info( appSuccessKey+userId);
        if (appointment == null)
            return ResponseResult.fail(AnnErrorEnum.CANCEL_FAILED.getCode(), "预约单不存在");
        try {
           int aResult = appointmentRepository.
                   updateUpdatedAtAndStatusBy(new Date(), Byte.parseByte("2"), appointmentDTO.getId());
           if (aResult == 1){
               //使用号 -1
               log.info(appointmentDTO.toString());
//               Integer sResult = clnicStaffService.updateClinicStaffSlotById(clnicId, -1);

                   //获取redis ttl
                   Boolean deleteR = redisTemplate.delete( appSuccessKey + userId);
                   if (deleteR){
                       //放回号池
                       redisTemplate.opsForList().leftPush("xxyy:doctor:"
                               +appointmentDTO.getStaffId()+":clnicId:"+clnicId,appointmentDTO.getSlotNo());
                       //删除订单处理投放到消息队列
                        kafkaTemplate.send(OrderTopics.ORDER_HANDLE_TOPIC,"deleteOrder"
                                , ((Appointment)appointment).getOrderId());
                        //处理库存

                        kafkaTemplate.send(ClinicStaffTopics.SLOT_USE_UPDATE,"++slot",appointmentDTO);
                        //处理死信队列键
                       delayMessageRedisTemplate.opsForHash().
                               delete( "delayed_messages",((Appointment) appointment).getOrderId().toString());
                        log.info("取消成功，预约单id为：{}",userId);

               }
           }
           //取消预约操作
       }catch (Exception e){
            log.info(  "取消失败" + e.getMessage());
            //可以放消息队列处理
            redisTemplate.opsForList().leftPop("xxyy:doctor:"+appointmentDTO.getStaffId()+":clnicId:"+clnicId);
            //设置过期时间
            redisTemplate.opsForValue().set(  appSuccessKey + userId,appointment,expire, TimeUnit.SECONDS);
           return  ResponseResult.fail(AnnErrorEnum.CANCEL_FAILED.getCode(), "取消失败");
       }

        return ResponseResult.success(true);
    }

    /**
     * 查询历史预约信息
     * @param userId
     * @return
     */
    @Override
    public ResponseResult<List<AppointmentVo>> getAppointmentList(Long userId) {
        //1.查出这个用户所有订单
        List<AppointmentVo> appointmentVos;
        try {
            List<Appointment> appointments = appointmentRepository.findByUserId(userId);
            // 根据部门医生id 查询所在部门信息
            if (appointments == null || appointments.isEmpty()) {
                return ResponseResult.success(List.of());
            }
            List<Long> staffId = appointments.stream().map(Appointment::getStaffId).toList();
            ResponseResult<List<DeptStaffVo>> byStaffIds = systemFeignClient.getByStaffIds(staffId);
            List<DeptStaffVo> data = byStaffIds.getData();
            // 获取 staffMap
            Map<Long, DeptStaffVo> staffMap = data == null ? Map.of()
                    : data.stream().collect(Collectors.toMap(DeptStaffVo::getStaffId,
                    Function.identity(), (a, b) -> a));
            appointmentVos = appointments.stream().map(a -> {
                System.out.println(staffMap);
                AppointmentVo vo = BeanCopyUtils.copyBean(a, AppointmentVo.class);
                // 关联医生信息
                DeptStaffVo s = staffMap.get(a.getStaffId());
                if (s != null) {
                    vo.setStaffName(s.getStaffName());
                    vo.setDeptName(s.getDeptName());
                }
                // 若 VO 字段是 String，做类型转换
                if (a.getOrderId() != null) vo.setOrderId(a.getOrderId());
                if (a.getUserId() != null)  vo.setUserId(a.getUserId());
                return vo;
            }).toList();

        }catch (Exception e){
            log.info("查询失败" + e.getMessage());
            return ResponseResult.fail(AnnErrorEnum.INTERNAL_SERVER_ERROR.getCode(), "查询失败");
        }

        return ResponseResult.success(appointmentVos);
    }

    @Override
    public ResponseResult<Void> updateAppointmentStatus(Long id,Byte status) {
        //预约状态修改
        try {
            int i = appointmentRepository.updateStatusById(status, id);
            if (i==1){
                return ResponseResult.success();
            }
        }catch (Exception e){
            log.info(e.getMessage());
            return ResponseResult.fail(6666, "修改预约状态失败失败");
        }
        return ResponseResult.success();
    }

    @Override
    public ResponseResult<Page<Appointment>> getPatientApp(AppointmentDTO appointmentDTO) {

        try {

            Pageable pageable = PageRequest.of
                    (appointmentDTO.getPageNum()-1,
                            appointmentDTO.getPageSize() == null ? 10 :
                                    appointmentDTO.getPageSize());            //根据预约单完成
            System.out.println(appointmentDTO);
            Date bookedAtEnd = DateUtils.target(appointmentDTO.getBookedAt(),23,00,00,999);


            Page<Appointment> appointmentPage = appointmentRepository
                    .search(appointmentDTO.getId(),
                            appointmentDTO.getStaffId(),
                            appointmentDTO.getPatientId(),
                            appointmentDTO.getUserId(),
                            appointmentDTO.getStatus(),
                            appointmentDTO.getClnicId(),
                            appointmentDTO.getSlotNo(),
                            appointmentDTO.getPeriod(),
                            appointmentDTO.getPatientName(),
                            appointmentDTO.getBookedAt(),
                            bookedAtEnd,
                            pageable);

            return ResponseResult.success(appointmentPage);
        }catch (Exception e){
            log.info(e.getMessage());
            return ResponseResult.fail(AnnErrorEnum.INTERNAL_SERVER_ERROR.getCode(), "查询失败");
        }

    }

    /**
     * 排队
     * @param appointmentDTO
     * @return
     */
    @Override
    public ResponseResult<Void> putSlotQueue(AppointmentDTO appointmentDTO) {


        int i = appointmentRepository.updateStatusByAId(Byte.parseByte("3"), appointmentDTO.getId());
        if (i==1){
            //更新预约单状态成功
            //存入redis
            redisTemplate.opsForList().rightPush
                    (putMyQueueKey(appointmentDTO.getStaffId(),appointmentDTO.getClnicId())
                            ,appointmentDTO);
        }
        return ResponseResult.success();
    }

    @Override
    public ResponseResult<List<AppointmentDTO>> getSlotQueue(Long staffId, Long clnicId) {
       try {

        List<Object> range = redisTemplate.opsForList().range(putMyQueueKey(staffId, clnicId), 0, -1);
        log.info("range:{}",range);
        if (range != null && !range.isEmpty()) {
            // 转换为 PatientAppVo 列表
            List<AppointmentDTO> list = range.stream().map(o -> ((AppointmentDTO) o)).toList();
            return ResponseResult.success(list);
        }
       }catch (Exception e){
           log.info(e.getMessage());
           return  ResponseResult.fail(321,"redis异常");
       }

        return ResponseResult.success(List.of());
    }

    /**
     * 修改预约状态显示已完成
     * @return
     */
    @Override
    public ResponseResult<Void> updatePatientAppStatus(PopQueueDTO popQueueDTO) {
        try {

            int i = appointmentRepository.updateStatusByAId((byte)4,popQueueDTO.getId());
            if (i==1){
                //更新预约单状态成功
                AppointmentDTO appointmentDTO = BeanCopyUtils.copyBean(popQueueDTO, AppointmentDTO.class);//存入redis

                //移除redis 队列指定元素
                Long removedCount = redisTemplate.
                        opsForList().
                        remove(putMyQueueKey(popQueueDTO.getStaffId(),popQueueDTO.getClnicId()),
                                1, appointmentDTO);
                System.out.println("移除成功数量：" + removedCount);
                return ResponseResult.success();
            }
        }catch (Exception e){
            log.info(e.getMessage());
            return ResponseResult.fail(6666, "修改预约状态失败失败");
        }
        return ResponseResult.fail(6666, "修改预约状态失败失败");
    }

    @Override
    public ResponseResult<Void> callSlot(Long id) {

        //查询预约信息
        Appointment appointment = appointmentRepository.findById(id).orElse(null);
        log.info("appointment:{}",appointment);
        if (appointment == null)
            return ResponseResult.fail(3333, "预约单不存在");
        Long userId = appointment.getUserId();
        //feign 调用
        ResponseResult<User> result = systemFeignClient.selectByUserId(userId);
        if (result.getCode() == 200){
            //调用成功
            //获取用户信息
            User data = result.getData();
            String userContact = data.getUserContact();
            Message message = new Message();
            message.setPhone(userContact);
            message.setContent("【云医通】到您啦 请迅速前往所在科室");
            //发送消息
            kafkaTemplateMsg.send(MessageTopics.MESSAGE_TOPICS,message);
            return ResponseResult.success();
        }else {
            //调用失败
            return ResponseResult.fail(3333, "获取用户信息失败");
        }

    }

    private  String putMyQueueKey(Long staffId,Long clnicId){
        return "queue:doctor:"+staffId+":clnicId:"+clnicId;
    }




}
